/*
 * Config
 *
 * - reads config parameters from properties file
 *   and stores it into memory (in static Hashtable)
 * - provides methods for reading particular property value
 *
 * Author: Damir Mazar, Interactive1
 *
 * Version: 1.2
 *
 * Date: 19/07/02
 *
 * History (only major changes go here):
 *   DD/MM/YY - <initials>, <company>
 *            - redefined whole XX algorithm
 *            - 5 new methods
 *
 *   30/11/00 - dm, i1 - created
 *	 11/07/02 - at, i1 - added methods getInt(String, int), getString(String, String)
 *	 19/07/02 - at, i1 - added refresh() method
 * Copyright: (c) 2000 Interactive1 Group Ltd
 */


package com.interactive1.utils;

import java.util.*;
import java.net.URL;
import java.io.*;

/**
* <p><code>Config</code> is a class for loading properties from configuration files
* (as standard Java properties files) stored somewhere in <code>CLASSPATH</code> and
* for accessing properties values.
* It is also possible to change values and store changes back to original file.
*
* <p>Config class loads properties files in memory when they are needed for the first time.
* All data remains in memory until recompilation of some class that uses it (JSP, servlet, ...).
*
* <p>Configuration files can be stored in any package/dir inside CLASSPATH and can have any extension.
* Default dir is <code>com.interactive1.config</code> and default extension is <code>.properties</code>.
*
* <p>One special file <code>Config.properties</code> can exists in <code>com.interactive1.config</code>
* and is used as default properties file. You can access properties from <code>Config.properties</code>
* like in following example:
*
* <p><code>
* import com.interactive1.utils.Config;<br>
* String alias = Config.mem.get("alias");<br>
* int debug = Config.mem.getInt("debug");
* </code>
*
* <p>If your properties are stored in <code>Measure.properties</code> file
* in <code>com.interactive1.config</code> package/dir
* you can get them as follows:
*
* <p><code>
* String alias = Config.mem.get("Measure", "alias");<br>
* int debug = Config.mem.getInt("Measure", "debug");
* </code>
*
* <p>If your properties are stored in <code>Ecards.cfg</code> file
* in <code>com.interactive1.ecards</code> package/dir
* you can get them as follows:
*
* <p><code>
* String alias = Config.mem.get("com/interactive1/ecards/Ecards.cfg", "alias");<br>
* int debug = Config.mem.getInt("com/interactive1/ecards/Ecards.cfg", "debug");
* </code>
*
* @author  Damir Mazar
* @version 1.2
*/


public class Config {

	public static boolean debug = false;

	public static final String DEF_PROPS_DIR = "com/interactive1/config/";
	public static final String DEF_PROPS_FILE = "Config";
	public static final String DEF_PROPS_EXT = ".properties";

	private static int _instanceCnt = 0;

	private Hashtable _htable = new Hashtable();
	private String _def_dir  = DEF_PROPS_DIR;
	private String _def_file = DEF_PROPS_FILE;
	private String _def_ext  = DEF_PROPS_EXT;

   /**
    * <code>mem</code> is static object through you can load and access all properties.
    *
    */
	public static Config mem = new Config();



   /**
    * Usually you don't need to instantiate <code>Config</code> objects - use <code>Config.mem</code> instance.
    *
    * @return String 	value of <code>key</code>
    *
    */
	public Config() {

		_instanceCnt++;
		if (debug) {
			System.out.println("com.interactive1.utils.Config.java: one instance loaded (" + _instanceCnt + ")");
		}
	}

	public Config(String def_dir, String def_file, String def_ext) {

		_def_dir  = def_dir;
		_def_file = def_file;
		_def_ext  = def_ext;

		_instanceCnt++;
		if (debug) {
			System.out.println("com.interactive1.utils.Config.java: one instance loaded (" + _instanceCnt + ")");
		}
	}



	// destructor (well, not exactly)
	protected void finalize() throws Throwable {

		_instanceCnt--;
		if (debug) {
			System.out.println("com.interactive1.utils.Config.java: one instance unloaded (" + _instanceCnt + " left)");
		}
		super.finalize();
	}


	/**
	 * Clears the cache from the memory, so that at the next call, the properties will be
	 * read from the config file.
	 *
	 * @since v1.2
	 */
	public void refresh() {
		_htable.clear();
	}

	public void refresh(String name) {
		_htable.remove(name);
	}

	// for finding properties file
	private String getFullFileName(String propsFileName) throws IOException {

		String fullFileName = propsFileName;

		if (fullFileName.indexOf('.') == -1) {
			fullFileName += _def_ext;
		}

		if (fullFileName.indexOf('/') == -1) {
			fullFileName = _def_dir + fullFileName;
		}

		return fullFileName;
	}


	// for loading properties from file
	synchronized private Properties loadProperties(String propsFileName) throws IOException {

		// while we are synchronized - check for properties in memory again
		Properties p = (Properties) _htable.get(propsFileName);
		if (p != null) {
			return p;
		}

		String fullFileName = getFullFileName(propsFileName);

		URL url = getClass().getClassLoader().getResource(fullFileName);
		if (url == null)
			throw new IOException("Can not find file: " + fullFileName);

		InputStream is = getClass().getClassLoader().getResourceAsStream(fullFileName);
		if (is == null)
			throw new IOException("Can not read file: " + fullFileName);

		p = new Properties();
		p.load(is);

		_htable.put(propsFileName, p);

		if (debug) {
			System.out.println("com.interactive1.utils.Config.java: loaded props(" + propsFileName + ") from: " + url);
		}

		return p;
	}



	// for loading properties from file
	synchronized public void save(String propsFileName) throws IOException, IllegalStateException, ClassCastException {

		// while we are synchronized - check for properties in memory again
		Properties p = (Properties) _htable.get(propsFileName);
		if (p == null) {
			throw new IllegalStateException("Properties not loaded: " + propsFileName);
		}

		String fullFileName = getFullFileName(propsFileName);

		URL url = getClass().getClassLoader().getResource(fullFileName);
		if (url == null)
			throw new IOException("Can not find file: " + fullFileName);

		p.store(new FileOutputStream(url.getFile()), "Saved with Config.mem.save(\"" + propsFileName+ "\")");

		if (debug) {
			System.out.println("com.interactive1.utils.Config.java: saved props(" + propsFileName + ") to: " + url);
		}

		return;
	}






	// methods for reading particular property

   /**
    * Returns the String value of property <code>key</code> from default <code>Config.property</code> file.
    *
    * @exception IOException	if properties file can not be found, opened or read
    *
    * @return String 	value of <code>key</code>
    *
    */
	public String get(String key) throws IOException {
		// from default _def_file file
		return get(_def_file, key);
	}

	/**
	 * Same as <code>get(String key)</code>.
	 * Returns the String value of property <code>key</code> from default <code>Config.property</code> file.
	 *
	 * @exception IOException	if properties file can not be found, opened or read
	 *
	 * @return String 	value of <code>key</code>
	 * @since v1.1
	 */
	public String getString(String key) throws IOException {
		// from default _def_file file
		return get(key);
	}
	
	/**
	 * Returns the String value of property <code>key</code> from default <code>Config.property</code> file.
	 * If there is no property with that name, or it contains an invalid value or null or an empty string, it returns
	 * a default value.
	 *
	 * @return String 	value of <code>key</code>
	 * @since v1.1
	 */
	public String getString(String key, String def) {
		// from default _def_file file
		try {
			String val = get(_def_file, key);
			if (val == null || val.trim().equals("")) val = def;
			return val;
		} catch (Exception e) {
			return def;	
		}
	}
	

   /**
    * Returns the String value of property <code>key</code> from given <code>propsFileName</code> file.
    * If <code>propsFileName</code> does not contains '/', then default dir is prepended.
    * If <code>propsFileName</code> does not contains '.', then default extension is added.
    *
    * @exception IOException	if properties file can not be found, opened or read
    *
    * @return String 	value of <code>key</code>
    */
	public String get(String propsFileName, String key) throws IOException {
		// from any file

		Properties p = (Properties) _htable.get(propsFileName);
		if (p == null) {
			p = loadProperties(propsFileName);
		}
		return p.getProperty(key);
	}





   /**
    * Returns the int value of property <code>key</code> from default <code>Config.property</code> file.
    *
    * @exception IOException	if properties file can not be found, opened or read
    *
    * @exception NumberFormatException	if there is no <code>key</code> in properties file or not a number
    *
    * @return int value of <code>key</code>.
    */
	public int getInt(String key) throws IOException, NumberFormatException {
		// from default _def_file file
		return Integer.parseInt(get(_def_file, key));
	}

	/**
	 * Returns the int value of property <code>key</code> from default <code>Config.property</code> file.
	 * If there is no property with that name, or it contains an invalid value, it returns
	 * a default value.
	 *
	 * @return int value of <code>key</code>.
	 * @since v1.1
	 */
	public int getInt(String key, int def) {
		// from default _def_file file
		try {
			return Integer.parseInt(get(_def_file, key));
		} catch (Exception e) {
			return def;
		}
	}


   /**
    * Returns the int value of property <code>key</code> from given <code>propsFileName</code> file.
    *
    * @exception IOException	if properties file can not be found, opened or read
    *
    * @exception NumberFormatException	if there is no <code>key</code> in properties file or not a number
    *
    * @return int value of <code>key</code>.
    *
    */
	public int getInt(String propsFileName, String key) throws IOException, NumberFormatException {
		// from any file
		return Integer.parseInt(get(propsFileName, key));
	}





	// methods for seting particular property

   /**
    * Sets the String <code>value</code> of property <code>key</code> from default <code>Config.property</code> file.
    *
    * @exception IOException	if properties file can not be found, opened or read
    *
    */
	public void set(String key, String value) throws IOException {
		// from default _def_file file

		set(_def_file, key, value);
		return;
	}


   /**
    * Sets the String <code>value</code> of property <code>key</code> from given <code>propsFileName</code> file.
    *
    * @exception IOException	if properties file can not be found, opened or read
    *
    */
	public void set(String propsFileName, String key, String value) throws IOException {
		// from any file

		Properties p = (Properties) _htable.get(propsFileName);
		if (p == null) {
			p = loadProperties(propsFileName);
		}
		p.setProperty(key, value);
		return;
	}





   /**
    * Sets the int <code>value</code> of property <code>key</code> from default <code>Config.property</code> file.
    *
    * @exception IOException	if properties file can not be found, opened or read
    *
    * @exception NumberFormatException	if there is no <code>key</code> in properties file or not a number
    *
    */
	public void setInt(String key, int value) throws IOException, NumberFormatException {
		// from default _def_file file

		set(_def_file, key, ("" + value));
		return;
	}

   /**
    * Sets the int <code>value</code> of property <code>key</code> from given <code>propsFileName</code> file.
    *
    * @exception IOException	if properties file can not be found, opened or read
    *
    * @exception NumberFormatException	if there is no <code>key</code> in properties file or not a number
    *
    */
	public void setInt(String propsFileName, String key, int value) throws IOException, NumberFormatException {
		// from any file

		set(propsFileName, key, ("" + value));
		return;
	}





   /**
    * Returns Enumeration of configuration parameters (keys) in given config file.
    *
    * @exception IOException	if properties file can not be found, opened or read
    *
    * @return Enumeration of configuration parameters.
    *
    */
	public Enumeration keys(String propsFileName) throws IOException {
		// from any file

		Properties p = (Properties) _htable.get(propsFileName);
		if (p == null) {
			p = loadProperties(propsFileName);
		}
		return p.keys();
	}


   /**
    * Returns Enumeration of configuration parameters (keys) in default config file.
    *
    * @exception IOException	if properties file can not be found, opened or read
    *
    * @return Enumeration of configuration parameters.
    *
    */
	public Enumeration keys() throws IOException {
		// from _def_file file

		return keys(_def_file);
	}

	public Properties getProperties(String propsFileName) throws IOException, NumberFormatException {
		Properties p = (Properties) _htable.get(propsFileName);
		if (p == null) {
			p = loadProperties(propsFileName);
		}
		return p;
	}

	public Properties getFile(String propsFileName) throws IOException{
		Properties p = (Properties) _htable.get(propsFileName);
		if (p == null) {
			p = loadPropertiesFile(propsFileName);
		}
		return p;
	}

	synchronized private Properties loadPropertiesFile(String fullFileName) throws IOException {

		// while we are synchronized - check for properties in memory again
		Properties p = (Properties) _htable.get(fullFileName);
		if (p != null) {
			return p;
		}

		InputStream is = new FileInputStream(fullFileName);
		if (is == null)
			throw new IOException("Can not read file: " + fullFileName);

		p = new Properties();
		p.load(is);

		_htable.put(fullFileName, p);

		if (debug) {
			System.out.println("com.interactive1.utils.Config.java: loaded props(" + fullFileName + ")");
		}

		return p;
	}

}
